package x.ovo.wechat.bot.plugin;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import x.ovo.wechat.bot.common.Constant;
import x.ovo.wechat.bot.common.Context;
import x.ovo.wechat.bot.common.entity.event.SystemEvent;
import x.ovo.wechat.bot.common.plugin.Plugin;
import x.ovo.wechat.bot.common.plugin.PluginDescription;
import x.ovo.wechat.bot.common.plugin.PluginManager;

import java.io.File;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Stream;

/**
 * 插件管理器
 *
 * @author ovo, created by 2024/05/01
 */
@Slf4j(topic = "插件管理器")
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class DefaultPluginManager implements PluginManager {
    /** 上下文 */
    @Getter private final Context context;
    /** 插件容器 */
    private final Map<String, Plugin> map = MapUtil.newHashMap();
    /** 群聊插件限制map */
    private  Map<String, Set<String>> groupPluginLimit ;

    public static PluginManager create(Context context) {
        DefaultPluginManager manager = new DefaultPluginManager(context);
        // 判断群插件配置文件是否存在，如果存在则加载
        if (FileUtil.exist(Constant.GROUP_PLUGIN_FILE)) {
            manager.groupPluginLimit = JSONUtil.readJSON(Constant.GROUP_PLUGIN_FILE, StandardCharsets.UTF_8).toBean(new TypeReference<>() { });
            log.info("群插件配置载入完成");
        } else {
            manager.groupPluginLimit = new HashMap<>();
        }
        manager.init();
        context.setPluginManager(manager);
        log.info("插件管理器初始化完成");
        return manager;
    }

    /**
     * 初始化
     */
    public void init() {
        // 遍历plugins目录下的所有jar文件
        log.info("开始载入插件...");
        File[] files = Constant.PLUGIN_DIR.listFiles((dir, name) -> name.endsWith(".jar"));
        if (null != files && files.length > 0) {
            Stream.of(files).forEach(this::loadPlugin);
            log.info("载入了 {} 个插件", this.map.size());
        } else {
            log.info("没有插件可加载");
        }
        this.context.getEventManager().callEvent(new SystemEvent(StrUtil.format("插件加载完成，共 {} 个插件", this.map.size())));
    }

    @Override
    public void loadPlugin(String pluginName) {
        File[] files = Constant.PLUGIN_DIR.listFiles((dir, name) -> name.endsWith(".jar"));
        if (null != files && files.length > 0) {
            Stream.of(files).filter(file -> {
                PluginDescription description = PluginLoader.getPluginDescription(file);
                return pluginName.equals(description.getName());
            }).forEach(this::loadPlugin);
            log.info("插件 [{}] 已成功加载", pluginName);
        } else {
            log.info("没有插件可加载");
        }
        this.context.getEventManager().callEvent(new SystemEvent(StrUtil.format("已加载 {} 个插件", this.map.size())));
    }

    /**
     * 加载插件
     *
     * @param file 插件jar文件
     */
    public void loadPlugin(File file) {
        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            PluginDescription description = PluginLoader.getPluginDescription(file);
            if (null == description) {
                log.error("插件 {} 加载失败，插件描述信息不存在", file.getName());
                return;
            }
            if (this.map.containsKey(description.getName())) {
                log.error("插件 {} 加载失败，插件已存在", file.getName());
                return;
            }

            log.info("正在加载插件 [{}]", description.getName());

            // 获取插件主类
            Plugin plugin = PluginLoader.loadPlugin(file, description);
            plugin.setContext(this.context);
            // 调用插件加载回调
            plugin.load();
            // 将插件存入容器
            this.map.put(description.getName(), plugin);
            // 获取插件中的事件监听器，注册到事件管理器中
            log.info("[{}] {}", description.getName(), plugin.isEnabled() ? "已启用" : "未启用");
            // 注册事件监听器
            this.context.getEventManager().register(plugin, plugin.getEventListener());
            // 注册命令执行器
            if (CollUtil.isNotEmpty(description.getCommands())) {
                description.getCommands().forEach(s -> {
                    this.context.getCommandManager().register(s, plugin.getCommandExecutor());
                    log.info("注册命令 [{}] 完成", s);
                });
            }
        } catch (Exception e) {
            log.error("插件 {} 加载失败", file.getName(), e);
        } finally {
            // 恢复原类加载器
            Thread.currentThread().setContextClassLoader(originalClassLoader);
        }
    }

    /**
     * 卸载插件
     *
     * @param name 名字
     */
    public void unloadPlugin(String name) {
        Plugin plugin = this.map.get(name);
        this.unloadPlugin(plugin);
    }

    /**
     * 卸载插件
     *
     * @param plugin 插件
     */
    @SneakyThrows
    public void unloadPlugin(Plugin plugin) {
        this.map.remove(plugin.getDescription().getName());
        this.context.getEventManager().unregister(plugin);
        ((URLClassLoader)plugin.getClassLoader()).close();
        log.info("卸载插件 {} 成功", plugin.getDescription().getName());
    }

    /**
     * 启用插件
     *
     * @param pluginName   插件名字
     * @param fromUserName 指令来源用户名
     */
    public void enablePlugin(String pluginName, String fromUserName) {
        // 从map中获取插件判断是否存在
        Plugin plugin = this.map.get(pluginName);
        Objects.requireNonNull(plugin);
        // 如果指令来源用户名为空，则启用插件，否则将插件名添加到群聊插件限制map中
        if (StrUtil.isBlank(fromUserName)) plugin.enable();
        else this.groupPluginLimit.computeIfAbsent(fromUserName, s -> new HashSet<>(4)).add(pluginName);
    }

    /**
     * 禁用插件
     *
     * @param pluginName   插件名字
     * @param fromUserName 指令来源用户名
     */
    public void disablePlugin(String pluginName, String fromUserName) {
        // 从map中获取插件判断是否存在
        Plugin plugin = this.map.get(pluginName);
        Objects.requireNonNull(plugin);
        // 如果指令来源用户名为空，则禁用插件，否则将插件名从群聊插件限制map中移除
        if (StrUtil.isBlank(fromUserName)) plugin.enable();
        else this.groupPluginLimit.computeIfAbsent(fromUserName, s -> new HashSet<>(4)).remove(pluginName);
    }

    /**
     * 获取所有插件名称
     *
     * @return {@link Collection}<{@link String}>
     */
    public Collection<String> getPlugins() {
        return this.map.entrySet().stream()
                .map(entry -> entry.getKey() + "    \t\t" + entry.getValue().isEnabled())
                .toList();
    }

    @Override
    public Plugin getPlugin(String name) {
        return this.map.get(name);
    }

    @Override
    public boolean isGroupEnabled(String groupName, Plugin plugin) {
        if (this.groupPluginLimit.containsKey(groupName)) {
            return this.groupPluginLimit.get(groupName).contains(plugin.getDescription().getName());
        }
        return false;
    }

    @Override
    public Set<String> listGroupPlugins(String groupName) {
        return this.groupPluginLimit.get(groupName);
    }

    @Override
    public void saveConfig() {
        FileUtil.writeBytes(JSONUtil.toJsonPrettyStr(this.groupPluginLimit).getBytes(), Constant.GROUP_PLUGIN_FILE);
        log.info("群插件配置写入完成");
    }

}
